Code Quality & Linting
Multiple layers catch issues at different stages of development. Earlier layers provide faster feedback; later layers ensure nothing slips through.
Defense in Depth: Three Layers
┌────────────────────────── ───────────────────────────────────────┐
│ LAYER 1: IDE / Agent Hooks │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ • Editor on-save (VSCode/Cursor/etc formatOnSave) │ │
│ │ • Claude Code and other agent's hooks │ │
│ │ When: As you type/save │ │
│ │ Catches: Format issues, syntax errors │ │
│ │ Status: PLANNED │ │
│ └───────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ LAYER 2: Git Hooks │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ • Pre-commit: auto-fix staged files │ │
│ │ • Pre-push: validate without fixing │ │
│ │ When: Before code leaves local machine │ │
│ │ Catches: Lint errors, format issues │ │
│ │ Status: ACTIVE │ │
│ └───────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ LAYER 3: CI Pipelines │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ • GitLab CI jobs on MR/push │ │
│ │ When: Code reaches remote repository │ │
│ │ Catches: Everything (full repo scan) │ │
│ │ Status: ACTIVE │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↓
↓
↓ Feedback Speed: Instant → Seconds → Minutes
↓ Scope: Single file → Changed files → Full repo
Layer 1: IDE/Agent Hooks (Planned)
This layer will provide instant feedback as you code. It will cover:
- Editor on-save hooks: VSCode/Cursor
formatOnSaveandcodeActionsOnSave - Claude Code hooks: Automated formatting/linting via Claude's hook system
Layer 2: Git Hooks
Git hooks catch issues before code leaves your machine. We use Lefthook to run formatters and linters on staged/pushed files.
Installation
Hooks run outside containers, so they need to be installed on your host machine.
First, ensure you have Lefthook installed:
brew install lefthook
Then run per repository:
lefthook install
Tools
| Tool | Purpose | Notes |
|---|---|---|
| Lefthook | Hook runner | Consistent behavior, deep customization. Install via brew, not npm |
| Duster | PHP lint/format | Default toolset (TLint, PHPCS, PHP-CS-Fixer, Pint). No duster.json needed |
| Larastan/PHPStan | Static analysis | Laravel rules on top of PHPStan. Level 6 default if no config |
| ESLint | JS linting | Stable ecosystem. May migrate to biome/oxc later |
| Prettier | Formatting | Stable, widely adopted. May migrate to biome/oxc later |
| tsc | TypeScript typecheck | Pre-push in TS repos |
Rules (Apply Everywhere)
- Hooks use
glob:filters,{staged_files}(pre-commit),{push_files}(pre-push) - Container repos: guard with
zoo check <service>and run viazex ... - Non-container repos: run via local
npx ... - No wrapper scripts. Run tools directly
- CI runs tools directly on full repo
Hook Strategy
We use a two-phase approach: pre-commit auto-fixes your code as you commit (fast feedback on staged files only), while pre-push validates without fixing (catches what slipped through and blocks bad pushes). This keeps commits clean while ensuring nothing broken reaches the remote.
Pre-commit
Runs formatters and fixers on staged files. Fixed files are auto-staged.
- JS/TS:
prettier --writetheneslint --fix || true - PHP:
duster fix(on failure, runduster lintfor details)
Pre-push
Validates without modifying. Blocks push on failure.
- JS/TS:
eslint(no--fix) - PHP:
phpstan analyse
See Linting References for complete config examples.
Hook UX Patterns
- Progress messages: echo "Running X..." before each tool for visibility
- Graceful skip: if container not running, warn and exit 0 (CI will catch issues)
- Fail with details: on duster fix failure, run lint to show what's wrong
Layer 3: CI Pipelines
CI is the final safety net - it runs on every push and MR, scanning the entire repo regardless of what was changed locally.
| Language | Jobs | Commands |
|---|---|---|
| JS | lint:js | eslint ., prettier --check . |
| PHP | lint:php, analyze:static:phpstan | ./vendor/bin/duster lint, ./vendor/bin/phpstan analyse |
- CI never relies on hooks - it runs tools directly on the full repo
Disabling Hooks Temporarily
# Disable all hooks
rm -f .git/hooks/*
# Re-enable
lefthook install
Exemplars
See Linting References for repo-specific configurations and examples.